/* Copyright (C) 2008-2022 Free Software Foundation, Inc.
   Contributed by Richard Henderson <rth@redhat.com>.

   This file is part of the GNU Transactional Memory Library (libitm).

   Libitm is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   Libitm is distributed in the hope that it will be useful, but WITHOUT ANY
   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
   FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
   more details.

   Under Section 7 of GPL version 3, you are granted additional
   permissions described in the GCC Runtime Library Exception, version
   3.1, as published by the Free Software Foundation.

   You should have received a copy of the GNU General Public License and
   a copy of the GCC Runtime Library Exception along with this program;
   see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
   <http://www.gnu.org/licenses/>.  */


#include "asmcfi.h"
#include "config.h"
#include "cet.h"

#define CONCAT1(a, b) CONCAT2(a, b)
#define CONCAT2(a, b) a ## b

#ifdef __USER_LABEL_PREFIX__
#  define SYM(x) CONCAT1 (__USER_LABEL_PREFIX__, x)
#else
#  define SYM(x) x
#endif

#ifdef __ELF__
#  define TYPE(x) .type SYM(x), @function
#  define SIZE(x) .size SYM(x), . - SYM(x)
#  ifdef HAVE_ATTRIBUTE_VISIBILITY
#    define HIDDEN(x) .hidden SYM(x)
#  else
#    define HIDDEN(x)
#  endif
#else
#  define TYPE(x)
#  define SIZE(x)
#  ifdef __MACH__
#    define HIDDEN(x) .private_extern SYM(x)
#  else
#    define HIDDEN(x)
#  endif
#endif

/* These are duplicates of the canonical definitions in libitm.h.  Note that
   the code relies on pr_uninstrumentedCode == a_runUninstrumentedCode.  */
#define pr_uninstrumentedCode	0x02
#define pr_hasNoAbort		0x08
#define pr_HTMRetryableAbort	0x800000
#define pr_HTMRetriedAfterAbort	0x1000000
#define a_runInstrumentedCode	0x01
#define a_runUninstrumentedCode	0x02
#define a_tryHTMFastPath	0x20

#define _XABORT_EXPLICIT	(1 << 0)
#define _XABORT_RETRY		(1 << 1)

	.text

	.align 4
	.globl	SYM(_ITM_beginTransaction)

SYM(_ITM_beginTransaction):
	cfi_startproc
	_CET_ENDBR
#ifdef __x86_64__
#ifdef HAVE_AS_RTM
	/* Custom HTM fast path.  We start the HW transaction here and let
	   gtm_thread::begin_transaction (aka GTM_begin_transaction) decide
	   how to proceed on aborts: We either retry the fast path, or fall
	   back to another execution method.  RTM restores all registers after
	   a HW transaction abort, so we can do the SW setjmp after aborts,
	   and we have to because we might choose a SW fall back.  However,
	   we have to explicitly save/restore the first argument (edi).
	   The htm_fastpath field is the second int in gtm_rwlock.  */
	cmpl	$0, (SYM(gtm_serial_lock)+4)(%rip)
	jz	.Lno_htm
	testl	$pr_hasNoAbort, %edi
	jz	.Lno_htm
.Lhtm_fastpath:
	xbegin	.Ltxn_abort
	/* Monitor the serial lock (specifically, the 32b writer/summary field
	   at its start), and only continue if there is no serial-mode
	   transaction.  Note that we might be just a nested transaction and
	   our outermost transaction might be in serial mode; we check for
	   this case in the retry policy implementation.  */
	cmpl	$0, SYM(gtm_serial_lock)(%rip)
	jnz	1f
	/* Now also check that HW transactions are still allowed to run (see
	   gtm_thread::begin_transaction for why this is necessary).  */
	cmpl	$0, (SYM(gtm_serial_lock)+4)(%rip)
	jz	1f
	/* Everything is good.  Run the transaction, preferably using the
	   uninstrumented code path.  Note that the following works because
	   pr_uninstrumentedCode == a_runUninstrumentedCode.  */
	andl	$pr_uninstrumentedCode, %edi
	mov	$a_runInstrumentedCode, %eax
	cmovnz	%edi, %eax
	ret
	/* There is a serial-mode transaction or HW transactions are not
	   allowed anymore, so abort (see htm_abort() regarding the abort
	   code).  */
1:	xabort	$0xff
.Ltxn_abort:
	/* If it might make sense to retry the HTM fast path, let the C++
	   code decide.  */
	testl	$(_XABORT_RETRY|_XABORT_EXPLICIT), %eax
	jz	.Lno_htm
	orl	$pr_HTMRetryableAbort, %edi
	/* Let the C++ code handle the retry policy.  */
.Lno_htm:
#endif
	leaq	8(%rsp), %rax
	subq	$72, %rsp
	cfi_adjust_cfa_offset(72)
	/* Store edi for future HTM fast path retries.  We use a stack slot
	   lower than the jmpbuf so that the jmpbuf's rip field will overlap
	   with the proper return address on the stack.  */
	movl	%edi, (%rsp)
	/* Save the jmpbuf for any non-HTM-fastpath execution method.
	   Because rsp-based addressing is 1 byte larger and we've got rax
	   handy, use it.  */
	movq	%rax, -72(%rax)
	movq	%rbx, -64(%rax)
	movq	%rbp, -56(%rax)
	movq	%r12, -48(%rax)
	movq	%r13, -40(%rax)
	movq	%r14, -32(%rax)
	movq	%r15, -24(%rax)
	xorq	%rdx, %rdx
	/* Save zero or shadow stack pointer in the new field.  */
#if defined __SHSTK__ && defined __CET__ && (__CET__ & 2) != 0
	rdsspq	%rdx
#endif
	movq	%rdx, -16(%rax)
	leaq	-72(%rax), %rsi
	call	SYM(GTM_begin_transaction)
	movl	(%rsp), %edi
	addq	$72, %rsp
	cfi_adjust_cfa_offset(-72)
#ifdef HAVE_AS_RTM
	/* If a_tryHTMFastPath was returned, then we need to retry the
	   fast path.  We also restore edi and set pr_HTMRetriedAfterAbort
	   to state that we have retried the fast path already (it's harmless
	   if this bit is set even if we don't retry the fast path because it
	   is checked iff pr_HTMRetryableAbort is set).  We clear
	   pr_HTMRetryableAbort because it applies to a previous HW
	   transaction attempt.  */
	cmpl	$a_tryHTMFastPath, %eax
	jnz	2f
	andl	$(0xffffffff-pr_HTMRetryableAbort), %edi
	orl	$pr_HTMRetriedAfterAbort, %edi
	jmp	.Lhtm_fastpath
2:
#endif
#else
	leal	4(%esp), %ecx
	movl	4(%esp), %eax
	subl	$28, %esp
	cfi_def_cfa_offset(32)
	movl	%ecx, 4(%esp)
	movl	%ebx, 8(%esp)
	movl	%esi, 12(%esp)
	movl	%edi, 16(%esp)
	movl	%ebp, 20(%esp)
	xorl	%edx, %edx
	/* Save zero or shadow stack pointer in the new field.  */
#if defined __SHSTK__ && defined __CET__ && (__CET__ & 2) != 0
	rdsspd	%edx
#endif
	movl	%edx, 24(%esp)
	leal	4(%esp), %edx
#if defined HAVE_ATTRIBUTE_VISIBILITY || !defined __PIC__
	call	SYM(GTM_begin_transaction)
#elif defined __ELF__
	call	1f
1:	popl	%ebx
	addl	$_GLOBAL_OFFSET_TABLE_+[.-1b], %ebx
	call	SYM(GTM_begin_transaction)@PLT
	movl	8(%esp), %ebx
#else
# error "Unsupported PIC sequence"
#endif
	addl	$28, %esp
	cfi_def_cfa_offset(4)
#endif
	ret
	cfi_endproc

	TYPE(_ITM_beginTransaction)
	SIZE(_ITM_beginTransaction)

	.align 4
	.globl	SYM(GTM_longjmp)

SYM(GTM_longjmp):
	cfi_startproc
	_CET_ENDBR
#ifdef __x86_64__
	movq	(%rsi), %rcx
	movq	8(%rsi), %rbx
	movq	16(%rsi), %rbp
	movq	24(%rsi), %r12
	movq	32(%rsi), %r13
	movq	40(%rsi), %r14
	movq	48(%rsi), %r15
	movl	%edi, %eax
	cfi_def_cfa(%rsi, 0)
	cfi_offset(%rip, 64)
	cfi_register(%rsp, %rcx)
	movq	%rcx, %rsp
#if defined __SHSTK__ && defined __CET__ && (__CET__ & 2) != 0
	/* Check if Shadow Stack is enabled.  */
	xorq	%rcx, %rcx
	rdsspq	%rcx
	testq	%rcx, %rcx
	je	.L1
	/* Calculate number of frames to skip.  */
	subq	56(%rsi), %rcx
	negq	%rcx
	shrq	$3, %rcx
	incq	%rcx
	/* If # of frames is greater 255 then loop
	   and adjust.  */
	cmpq	$255, %rcx
	jbe	.L3
	movl	$255, %edi
	.p2align 4,,10
	.p2align 3
.L4:
	incsspq	%rdi
	subq	$255, %rcx
	cmpq	$255, %rcx
	ja	.L4
.L3:
	incsspq	%rcx
.L1:
#endif
	jmp	*64(%rsi)
#else
	movl	(%edx), %ecx
	movl	4(%edx), %ebx
	movl	8(%edx), %esi
	movl	12(%edx), %edi
	movl	16(%edx), %ebp
	cfi_def_cfa(%edx, 0)
	cfi_offset(%eip, 24)
	cfi_register(%esp, %ecx)
	movl	%ecx, %esp
#if defined __SHSTK__ && defined __CET__ && (__CET__ & 2) != 0
	/* Check if Shadow Stack is enabled.  */
	xorl	%ecx, %ecx
	rdsspd	%ecx
	testl	%ecx, %ecx
	je	.L1
	/* Calculate # of frames to skip.  */
	subl	20(%edx), %ecx
	negl	%ecx
	shrl	$2, %ecx
	incl	%ecx
	/* If # of frames is greater 255 then loop
	   and adjust.  */
	cmpl	$255, %ecx
	jbe	.L3
	pushl	%eax
	movl	$255, %eax
	.p2align 4,,10
	.p2align 3
.L4:
	incsspd	%eax
	subl	$255, %ecx
	cmpl	$255, %ecx
	ja	.L4
	popl	%eax
.L3:
	incsspd	%ecx
.L1:
#endif
	jmp	*24(%edx)
#endif
	cfi_endproc

	TYPE(GTM_longjmp)
	HIDDEN(GTM_longjmp)
	SIZE(GTM_longjmp)

#ifdef __linux__
.section .note.GNU-stack, "", @progbits
#endif
